using System;
using System.Xml;
using EnvDTE80;
using JetBrains.ReSharper.Metadata.Access;
using JetBrains.ReSharper.Metadata.Reader.API;
using JetBrains.ReSharper.ProjectModel;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.UnitTestSupport.PluginInterface;
using JetBrains.ReSharper.Util;
using log4net;

namespace JamesKovacs.ReSharper.UnitTestSupport.VstsUnit {
    [UnitTestCodeExplorer("VstsUnit")]
    public class VstsUnitCodeExplorer : IUnitCodeExplorer {
        private static readonly ILog logger = LogManager.GetLogger(typeof(VstsUnitCodeExplorer));
        private const string VstsUnitTestingAssemblyName = "Microsoft.VisualStudio.QualityTools.UnitTestFramework";
        private CLRTypeName ignoreAttribute = new CLRTypeName("Microsoft.VisualStudio.TestTools.UnitTesting.IgnoreAttribute");
        private CLRTypeName testClassAttribute = new CLRTypeName("Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute");
        private CLRTypeName testMethodAttribute = new CLRTypeName("Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute");

        public VstsUnitCodeExplorer() {
            VstsUnitSetup.Initialize();
        }

        public bool IsTestFixture(ITypeElement element) {
            try {
                if(element == null) {
                    return false;
                }
                return element.HasAttributeInstance(testClassAttribute, false);
            } catch(Exception ex) {
                logger.Error("Exception thrown in IsTestFixture(ITypeElement)", ex);
                return false;
            }
        }

        private bool IsTestFixture(IMetadataTypeInfo typeInfo) {
            if(typeInfo == null) {
                throw new ArgumentNullException("typeInfo");
            }
            return typeInfo.HasCustomAttribute(testClassAttribute.ClrName);
        }

        public bool IsTestFixture(CodeClass2 codeClass) {
            try {
                if(codeClass == null) {
                    return false;
                }
                foreach(CodeAttribute2 attribute in codeClass.Attributes) {
                    if(attribute.FullName == testClassAttribute.ClrName) {
                        return true;
                    }
                }
                return false;
            } catch(Exception ex) {
                logger.Error("Exception thrown in IsTestFixture(CodeClass2)", ex);
                return false;
            }
        }

        public bool IsTestMethod(ITypeMember element) {
            try {
                if(element == null) {
                    return false;
                }
                return element.HasAttributeInstance(testMethodAttribute, false);
            } catch(Exception ex) {
                logger.Error("Exception thrown in IsTestMethod(ITypeMember)", ex);
                return false;
            }
        }

        private bool IsTestMethod(IMetadataMethod method) {
            if(method == null) {
                throw new ArgumentNullException("method");
            }
            return method.HasCustomAttribute(testMethodAttribute.ClrName);
        }

        public bool IsTestMethod(CodeFunction2 codeFunction) {
            try {
                if(codeFunction == null) {
                    return false;
                }
                foreach(CodeAttribute2 attribute in codeFunction.Attributes) {
                    if(attribute.FullName == testMethodAttribute.ClrName) {
                        return true;
                    }
                }
                return false;
            } catch(Exception ex) {
                logger.Error("Exception thrown in IsTestFixture(CodeClass2)", ex);
                return false;
            }
        }

        public XmlDocument Explore(IMetadataAssembly assembly) {
            try {
                XmlDocument document = new XmlDocument();
                XmlElement assemblyElement = XmlUtil.CreateElement(document, "Assembly");
                XmlUtil.CreateAttributeWithValue(assemblyElement, "Name", assembly.AssemblyName.FullName);
                foreach(IMetadataTypeInfo typeInfo in assembly.GetTypes()) {
                    if(IsTestFixture(typeInfo)) {
                        XmlElement classElement = XmlUtil.CreateElement(assemblyElement, "Fixture");
                        XmlUtil.CreateAttributeWithValue(classElement, "Name", typeInfo.FullyQualifiedName);
                        if(typeInfo.HasCustomAttribute(ignoreAttribute.ClrName)) {
                            XmlUtil.CreateAttributeWithValue(classElement, "State", TestFixtureState.IGNORED.ToString());
                        }
                        foreach(IMetadataMethod methodInfo in typeInfo.GetMethods()) {
                            if(IsTestMethod(methodInfo)) {
                                XmlElement methodElement = XmlUtil.CreateElement(classElement, "Method");
                                XmlUtil.CreateAttributeWithValue(methodElement, "Name", methodInfo.Name);
                                if(methodInfo.HasCustomAttribute(ignoreAttribute.ClrName)) {
                                    XmlUtil.CreateAttributeWithValue(methodElement, "State", TestFixtureState.IGNORED.ToString());
                                }
                            }
                        }
                    }
                }
                return document;
            } catch(Exception ex) {
                logger.Error("Exception thrown in Explore(IMetadataAssembly)", ex);
                return null;
            }
        }

        public bool Vote(IMetadataAssembly assembly) {
            try {
                foreach(AssemblyReference reference in assembly.ReferencedAssembliesNames) {
                    if(string.Compare(reference.AssemblyName.Name, VstsUnitTestingAssemblyName, StringComparison.InvariantCultureIgnoreCase) == 0) {
                        return true;
                    }
                }
                return false;
            } catch(Exception ex) {
                logger.Error("Exception thrown in Vote(IMetadataAssembly)", ex);
                return false;
            }
        }

        public bool Vote(IProject project) {
            try {
                foreach(IAssemblyReference reference in project.GetAssemblyReferences()) {
                    if(string.Compare(reference.Name, VstsUnitTestingAssemblyName, StringComparison.InvariantCultureIgnoreCase) == 0) {
                        return true;
                    }
                }
                return false;
            } catch(Exception ex) {
                logger.Error("Exception thrown in Vote(IProject)", ex);
                return false;
            }
        }
    }
}